#include <REG51.H>
#include <LCD1602.h>
#include <intrins.h>
#include <delay.h>

/*******************************************************************************************************/

#define __LCD_data_port__ P0 /* LCD1602的数据端口 */
sbit rs_LCD = P3 ^ 5;
sbit rw_LCD = P3 ^ 6;
sbit e_LCD = P3 ^ 7;

/*******************************************************************************************************/

/**
 * @brief  LCD1602判忙
 * @param  None
 * @retval None
 */
static bit lcd_bz(void)
{
    bit result;
    rs_LCD = 0; // 读忙信号
    rw_LCD = 1;
    e_LCD = 0;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    e_LCD = 1;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    result = (bit)(__LCD_data_port__ & 0x80);
    e_LCD = 0;
    return result;
}

/**
 * @brief  对 LCD1602 写命令
 * @param  cmd: 写入LCD1602的命令
 * @retval None
 */
static void lcd_wcmd(unsigned char cmd)
{
    while (lcd_bz())
        ;
    rs_LCD = 0;
    rw_LCD = 0;
    __LCD_data_port__ = cmd;
    e_LCD = 0;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    e_LCD = 1;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    e_LCD = 0;
}

/**
 * @brief  设置 LCD1602 光标位置，原点在屏幕左上角。
 * @param  x: x轴坐标，x取值范围 0-15。
 * @param  y: y轴坐标，y取值范围 0-3。
 * @retval None
 */
void lcd_set_pos(unsigned char x, unsigned char y)
{
    x &= 0x0f; // 列地址限制在0-15间
    y &= 0x03; // 行地址限制在0-3间

    if (y == 0)
    {
        lcd_wcmd(x + 0x80);
    }
    else if (y == 1)
    {
        lcd_wcmd((x + 0x40) | 0x80);
    }
    else if (y == 2)
    {
        lcd_wcmd((x + 0x10) | 0x80);
    }
    else
    {
        lcd_wcmd((x + 0x50) | 0x80);
    }
}

/**
 * @brief  LCD1602显示字符
 * @param  dat: (char) 字符
 * @retval None
 */
void lcd_wchar(unsigned char dat)
{
    while (lcd_bz())
        ;
    rs_LCD = 1;
    rw_LCD = 0;
    __LCD_data_port__ = dat;
    e_LCD = 0;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    e_LCD = 1;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    e_LCD = 0;
}

/**
 * @brief  LCD1602显示字符串
 * @param  str: 字符串指针(char*)
 * @retval None
 */
void lcd_wstring(char str[])
{
    unsigned char i;
    for (i = 0; str[i] != '\0'; i++)
    {
        lcd_wchar(str[i]);
    }
}

#ifdef __LCD1602_SUPPORT_printf__
/**
 * @brief  printf 函数输出重定向
 * @param  c: (char) 字符
 * @retval 传入参数c
 */
char putchar(char c)
{
    lcd_wchar(c);
    return c;
}
#endif

/**
 * @brief  LCD 光标复位，清屏。
 * @param  None
 * @retval None
 */
void lcd_clear(void)
{
    lcd_wcmd(0x01); // 清屏命令
    delay_ms(5);    // 这个延时很重要
}

/**
 * @brief  LCD1602 初始化
 * @param  None
 * @retval None
 */
void lcd_init(void)
{
    lcd_wcmd(0x38); // 八位数据接口，两行显示，5*7点阵
    lcd_wcmd(0x0c); // 显示开，光标关，闪烁关
    lcd_wcmd(0x06); // 数据读写操作后，光标自动加一，画面不动
    lcd_wcmd(0x01); // 光标复位，清屏
    delay_ms(5);    // 这个延时很重要
}
